﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using Blue.ResourceManager;
using EngineOfEvermore.Imaging;
using EngineOfEvermore.Editor.Common;

namespace EngineOfEvermore.Editor.ImageMapEditor
{
    internal partial class ImageMapInstance : DockContent
    {
        #region Variables
        String _referencedImageMap;
        String _imageResourceName;
        Bitmap _originalImage;
        Point _pictureOffset;
        Point _cameraPosition;

        List<ImageMap.IFrameGenerator> _frameGeneratorList;
        ImageMap.IFrameGenerator _activeFrameGenerator;
        List<Rectangle> _frames = new List<Rectangle>();
        int _zoomLevel;
        float _zoomFactor = 1.0f;
        bool _draggingImage;
        Point _lastMouseLocation;
        ImageMapEditor _imageMapEditor;

        UndoRecord _undoRecord = new UndoRecord();
        int _savedInstanceVersion = -1;
        bool _neverSaved = true;
        String _title;
        List<Bitmap> _frameNumberCache = new List<Bitmap>();
        #endregion

        #region UndoAction classes
        class FrameGeneratorUpdatedUndoAction : UndoAction
        {
            Object _oldValue;
            Object _newValue;
            GridItem _gridItem;
            PropertyDescriptor _rootPropertyDescriptor;
            Object _targetObject;
            ImageMapInstance _owner;
            bool _clearFrames;

            public FrameGeneratorUpdatedUndoAction(
                Object oldValue,
                Object newValue,
                GridItem gridItem,
                Object targetObject,
                ImageMapInstance owner,
                bool clearFrames
                )
            {
                _targetObject = targetObject;
                _owner = owner;
                _clearFrames = clearFrames;
                _gridItem = gridItem;

                List<PropertyDescriptor> descs = new List<PropertyDescriptor>();

                // Get the list of property descriptions from this grid item
                // to the root property grid item
                while ( gridItem.GridItemType == GridItemType.Property )
                {
                    descs.Add( gridItem.PropertyDescriptor );
                    gridItem = gridItem.Parent;
                }

                // Sort it from root to leaf
                descs.Reverse();

                // This is the root newValue
                _rootPropertyDescriptor = descs[0];
                _newValue = _rootPropertyDescriptor.GetValue( _targetObject );

                // Build the subvalues of the roots current value
                List<Object> values = new List<Object>();
                values.Add( descs[0].GetValue( _targetObject ) );

                for ( int i = 1; i < descs.Count; ++i )
                {
                    Object value = descs[i].GetValue( values[i - 1] );
                    values.Add( value );
                }

                // Set the leaf value to the old value
                values[values.Count - 1] = oldValue;

                // Rebuild the whole oldValue from its subvalues
                for ( int i = descs.Count - 1; i > 0; --i )
                {
                    descs[i].SetValue( values[i - 1], values[i] );
                }

                // Now values[0] should be the root oldValue
                _oldValue = values[0];
            }

            #region UndoAction Members

            public void Undo()
            {
                _rootPropertyDescriptor.SetValue( _targetObject, _oldValue );
                _gridItem.Select();

                if ( _clearFrames )
                {
                    _owner._frames.Clear();
                }
                else
                {
                    String statusMessage = _owner._imageMapEditor.StatusMessage;
                    _owner._updateFrameGeneration();
                    _owner._imageMapEditor.StatusMessage = statusMessage;
                }

                _owner._updateVisuals();
            }

            public void Redo()
            {
                _rootPropertyDescriptor.SetValue( _targetObject, _newValue );
                _gridItem.Select();

                _owner._updateFrameGeneration();
                _owner._updateVisuals();
            }

            public bool IgnoreNextAction { get { return false; } }
            #endregion
        }

        class FrameGeneratorChangedUndoAction : UndoAction
        {
            ImageMapInstance _owner;
            ImageMap.IFrameGenerator _oldGen;
            ImageMap.IFrameGenerator _newGen;

            public FrameGeneratorChangedUndoAction(
                ImageMapInstance owner,
                ImageMap.IFrameGenerator oldGen,
                ImageMap.IFrameGenerator newGen
                )
            {
                _owner = owner;
                _oldGen = oldGen;
                _newGen = newGen;
            }

            #region UndoAction Members

            public void Undo()
            {
                _owner._imageMapEditor.OnFrameGeneratorChanged( _oldGen );
            }

            public void Redo()
            {
                _owner._imageMapEditor.OnFrameGeneratorChanged( _newGen );
            }

            public bool IgnoreNextAction
            {
                get { return true; }
            }

            #endregion
        }
        #endregion

        #region Properties
        public int ZoomLevel
        {
            get { return _zoomLevel; }
            set
            {
                _zoomLevel = value;

                if ( _zoomLevel > 5 )
                    _zoomLevel = 5;

                if ( _zoomLevel < -5 )
                    _zoomLevel = -5;

                _zoomFactor = (float) Math.Pow( 2, _zoomLevel );
            }
        }

        public String ImageResourceName
        {
            get { return _imageResourceName; }
        }

        public Size ImageSize
        {
            get { return _originalImage.Size; }
        }

        public ImageMapEditor ImageMapEditor
        {
            get { return _imageMapEditor; }
            set { _imageMapEditor = value; }
        }

        public List<ImageMap.IFrameGenerator> FrameGeneratorList
        {
            get { return _frameGeneratorList; }
        }

        public ImageMap.IFrameGenerator ActiveFrameGenerator
        {
            get { return _activeFrameGenerator; }
            set 
            {
                Debug.Assert( _frameGeneratorList.Contains( value ) || value == null );

                if ( value != null && _activeFrameGenerator != null )
                {
                    UndoAction undoAction = new FrameGeneratorChangedUndoAction(
                        this, _activeFrameGenerator, value
                        );

                    _undoRecord.AddUndoAction( undoAction );
                }

                _activeFrameGenerator = value;

                bool failed = false;

                try
                {
                    _updateFrameGeneration();
                }
                catch ( Exception )
                {
                    failed = true;
                }

                if ( failed )
                {
                    _frames.Clear();
                }

                _updateVisuals();
                _updateTitle();
            }
        }

        public String ReferencedImageMap
        {
            get { return _referencedImageMap; }
            set 
            { 
                _referencedImageMap = value;
                _title = value;
                _updateTitle();
            }
        }

        public bool HasUnsavedChanges
        {
            get { return _neverSaved || _savedInstanceVersion != _undoRecord.Version; }
        }

        public bool NeverSaved
        {
            get { return _neverSaved; }
        }

        public bool CanUndo
        {
            get { return _undoRecord.CanUndo; }
        }

        public bool CanRedo
        {
            get { return _undoRecord.CanRedo; }
        }
        #endregion

        #region Constructor
        public ImageMapInstance()
        {
            InitializeComponent();

            _frameGeneratorList = new List<ImageMap.IFrameGenerator>();

            foreach ( Type t in ImageMap.GetFrameGeneratorTypes() )
            {
                Object o = TypeDescriptor.CreateInstance( null, t, null, null );

                _frameGeneratorList.Add( (ImageMap.IFrameGenerator) o );
            }
        }
        #endregion

        #region Methods
        public void Undo()
        {
            _undoRecord.Undo();
            _updateTitle();
        }

        public void Redo()
        {
            _undoRecord.Redo();
            _updateTitle();
        }

        public void SaveImageMapAsResource( String resourceName )
        {
            Debug.Assert( _activeFrameGenerator != null );
            Debug.Assert( ResourceManager.ResourceExists( _imageResourceName ) );

            ImageMap.SaveAsResource( resourceName, _activeFrameGenerator, _imageResourceName, null );

            _savedInstanceVersion = _undoRecord.Version;
            _neverSaved = false;

            _referencedImageMap = resourceName;
            _title = resourceName;
            _updateTitle();
        }

        public void OnShowFrameNumberChanged()
        {
            _updateVisuals();
        }

        public void OnFrameNumberColorChanged()
        {
            _clearFrameNumberCache();
            _updateVisuals();
        }

        public void OnFrameOutlineColorChanged()
        {
            _updateVisuals();
        }

        public void OnImageResourceNameChanged( String newName )
        {
            Debug.Assert( ResourceManager.ResourceExists( newName ) );

            _imageResourceName = newName;
        }

        void _pictureBox_MouseWheel( object sender, MouseEventArgs e )
        {
            _cameraPosition = _pictureBoxToSourceImage( e.Location );
            
            ZoomLevel -= Math.Sign( e.Delta );

            _cameraPosition.X -= (int) ( 0.5f * _pictureBox.Width / _zoomFactor );
            _cameraPosition.Y -= (int) ( 0.5f * _pictureBox.Height / _zoomFactor );
            _correctCameraPosition();

            _updateVisuals();
        }

        public static ImageMapInstance CreateFromImageFile( String filename, 
            ImageMapEditor imageMapEditor )
        {
            ImageMapInstance inst = new ImageMapInstance();

            using ( Stream imageStream = ResourceManager.OpenStream( filename, ResourceOpenMode.Read ) )
            {
                using ( Image source = Bitmap.FromStream( imageStream ) )
                {
                    inst._originalImage = new Bitmap( source.Width, source.Height );
                    
                    using ( Graphics g = Graphics.FromImage( inst._originalImage ) )
                    {
                        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
                        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

                        g.DrawImage( source,
                            new Rectangle( 0, 0, source.Width, source.Height ),
                            new Rectangle( 0, 0, source.Width, source.Height ),
                            GraphicsUnit.Pixel 
                            );
                    }

                    inst._originalImage.Save( "xxxxxx.png", 
                        System.Drawing.Imaging.ImageFormat.Png );
                }
            }

            inst.ImageMapEditor = imageMapEditor;
            inst._imageResourceName = filename;

            inst._pictureBox.MouseWheel += new MouseEventHandler( inst._pictureBox_MouseWheel );
            inst.MouseWheel += new MouseEventHandler( inst._pictureBox_MouseWheel );

            inst.ImageMapEditor.OnImageMapInstanceCreated( inst );

            inst._title = Path.GetFileNameWithoutExtension( filename ) + ".ximp";

            inst._updateTitle();

            return inst;
        }


        /// <summary>
        /// Loads an ImageMapInstance from a *.ximp resource file.
        /// </summary>
        /// <param directoryName="node"></param>
        /// <param directoryName="imageMapEditor"></param>
        /// <returns></returns>
        public static ImageMapInstance CreateFromImageMapResource( String resourceName,
            ImageMapEditor imageMapEditor )
        {
            ImageMapInstance inst = new ImageMapInstance();

            ImageMap.IFrameGenerator frameGenerator;
            String imageName;
            Size? sizeHint;

            // Load the xml definition
            try
            {
                using ( Stream xmlStream = ResourceManager.OpenStream( resourceName, ResourceOpenMode.Read ) )
                {
                    ImageMap.LoadFromXmlStream( xmlStream, out frameGenerator,
                        out imageName, out sizeHint );
                }
            }
            catch ( Exception ex )
            {
                imageMapEditor.StatusMessage = ex.Message;
                throw;
            }

            if ( !ResourceManager.ResourceExists( imageName ) )
            { 
                imageMapEditor.StatusMessage = "The image " + imageName + 
                    " referenced by this ImageMap could not be found";
                throw new FileNotFoundException();
            }

            // Load the image
            using ( Stream imageStream = ResourceManager.OpenStream( imageName, ResourceOpenMode.Read ) )
            {
                using ( Image source = Bitmap.FromStream( imageStream ) )
                {
                    inst._originalImage = new Bitmap( source.Width, source.Height );

                    using ( Graphics g = Graphics.FromImage( inst._originalImage ) )
                    {
                        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
                        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

                        g.DrawImage( 
                            source,
                            new Rectangle( 0, 0, source.Width, source.Height ),
                            new Rectangle( 0, 0, source.Width, source.Height ),
                            GraphicsUnit.Pixel
                            );
                    }
                }
            }

            inst.ImageMapEditor = imageMapEditor;
            inst._imageResourceName = imageName;
            inst.ReferencedImageMap = resourceName;

            int fgIndex = inst._frameGeneratorList.FindIndex(
                x => x.GetType() == frameGenerator.GetType() 
                );

            Debug.Assert( fgIndex >= 0 );

            inst._frameGeneratorList[fgIndex] = frameGenerator;
            inst._activeFrameGenerator = frameGenerator;

            inst._pictureBox.MouseWheel += new MouseEventHandler( inst._pictureBox_MouseWheel );
            inst.MouseWheel += new MouseEventHandler( inst._pictureBox_MouseWheel );

            inst.ImageMapEditor.OnImageMapInstanceCreated( inst );

            inst._neverSaved = false;
            inst._title = resourceName;
            inst._updateTitle();

            inst._updateFrameGeneration();
            inst._updateVisuals();

            return inst;
        }

        public void OnFrameGeneratorUpdated( PropertyValueChangedEventArgs e )
        {
            bool failed = false;
            bool clearFramesOnUndo = _frames.Count == 0;
            try
            {
                _updateFrameGeneration();
            }
            catch ( Exception ex )
            {
                _imageMapEditor.StatusMessage = ex.Message;
                failed = true;
            }

            UndoAction undoAction =
                    new FrameGeneratorUpdatedUndoAction( e.OldValue,
                                                         e.ChangedItem.Value,
                                                         e.ChangedItem,
                                                         _activeFrameGenerator,
                                                         this,
                                                         clearFramesOnUndo );

            _undoRecord.AddUndoAction( undoAction );

            if ( failed )
            {
                _undoRecord.Undo();
                _undoRecord.Truncate();
            }

            _updateVisuals();
            _updateTitle();
        }

        void _adjustPictureBoxBitmapSize()
        {
            if ( _pictureBox.Image != null )
            {
                if ( _pictureBox.Size != _pictureBox.Image.Size && !_pictureBox.Size.IsEmpty )
                {
                    _pictureBox.Image.Dispose();
                    _pictureBox.Image = new Bitmap( _pictureBox.Width, _pictureBox.Height );
                }
            }
            else
            {
                _pictureBox.Image = new Bitmap( _pictureBox.Width, _pictureBox.Height );
            }
        }

        void _drawBitmapToPictureBox( bool refreshPictureBox )
        {
            if ( _originalImage == null )
                return;

            _adjustPictureBoxBitmapSize();

            using ( Graphics g = Graphics.FromImage( _pictureBox.Image ) )
            {
                g.Clear( Color.Transparent );

                int bitmapWidth = (int) (_originalImage.Width * _zoomFactor );
                int bitmapHeight = (int) ( _originalImage.Height * _zoomFactor );

                if ( bitmapWidth <= 0 || bitmapHeight <= 0 )
                {
                    _pictureBox.Refresh();
                    return;
                }

                Rectangle sourceRect = new Rectangle( Point.Empty, _originalImage.Size );
                Rectangle destRect = new Rectangle();

                destRect.Width = bitmapWidth;
                destRect.Height = bitmapHeight;

                if ( bitmapWidth < _pictureBox.Width )
                {
                    destRect.X = _pictureBox.Width / 2 - bitmapWidth / 2;
                }
                else
                {
                    destRect.X = (int) ( -_cameraPosition.X * _zoomFactor );
                }

                if ( bitmapHeight < _pictureBox.Height )
                {
                    destRect.Y = _pictureBox.Height / 2 - bitmapHeight / 2;
                }
                else
                {
                    destRect.Y = (int) ( -_cameraPosition.Y * _zoomFactor );
                }

                _pictureOffset = destRect.Location;

                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
                g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;

                g.DrawImage( _originalImage, destRect );
            }

            if ( refreshPictureBox )
            {
                _pictureBox.Refresh();
            }
        }

        void _updateFrameGeneration()
        {
            if ( _activeFrameGenerator == null )
            {
                _frames.Clear();
                return;
            }

            List<Rectangle> frames = new List<Rectangle>();

            try
            {
                frames.AddRange( new List<Rectangle>(
                    _activeFrameGenerator.GenerateFrames( _originalImage ) 
                    ) 
                );
            }
            catch ( Exception e )
            {
                // log error to status bar ...
                _imageMapEditor.StatusMessage = e.Message;
                throw;
            }

            _frames = frames;
            _imageMapEditor.StatusMessage = "Frame-Generation succeeded";
        }

        void _drawFrameRects()
        {
            if ( ! _frames.Any() )
                return;

            float zoomFactor = (float) Math.Pow( 2, _zoomLevel );

            using ( Graphics g = Graphics.FromImage( _pictureBox.Image ) )
            {
                g.InterpolationMode = 
                    System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

                using ( Pen pen = new Pen( _imageMapEditor.Config.FrameOutlineColorRt, 
                    zoomFactor ) )
                {
                    foreach ( Rectangle rect in _frames )
                    {
                        float left = rect.X * zoomFactor + _pictureOffset.X;
                        float top = rect.Y * zoomFactor + _pictureOffset.Y;
                        float width = rect.Width * zoomFactor;
                        float height = rect.Height * zoomFactor;
                        
                        Rectangle zoomedRect = new Rectangle( (int) left, (int) top,
                            (int) width, (int) height );

                        g.DrawRectangle( pen, zoomedRect );
                    }
                }
            }
        }

        void _drawFrameNumbers()
        {
            if ( !_frames.Any() )
                return;

            using ( Graphics g = Graphics.FromImage( _pictureBox.Image ) )
            {
                g.InterpolationMode =
                    System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                
                using ( Font font = new Font( "Arial", 2 * _zoomFactor ) )
                {
                    int i = 0;
                    foreach ( Rectangle rect in _frames )
                    {
                        float left = rect.X * _zoomFactor + _pictureOffset.X;
                        float top = rect.Y * _zoomFactor + _pictureOffset.Y;
                        float width = rect.Width * _zoomFactor;
                        float height = rect.Height * _zoomFactor;
                        RectangleF zoomedRect = new RectangleF( left, top, width, height );

                        String text = i.ToString();
                        SizeF stringSize = g.MeasureString( text, font );
                        float sizeRatio = stringSize.Width / stringSize.Height;

                        RectangleF fontRect = new RectangleF();

                        fontRect.Height = zoomedRect.Height * 0.8f;
                        fontRect.Width = fontRect.Height * sizeRatio;

                        if ( fontRect.Width > zoomedRect.Width )
                        {
                            fontRect.Width = zoomedRect.Width * 0.8f;
                            fontRect.Height = fontRect.Width / sizeRatio;
                        }

                        fontRect.X = zoomedRect.X + zoomedRect.Width * 0.5f - fontRect.Width * 0.5f;
                        fontRect.Y = zoomedRect.Y + zoomedRect.Height * 0.5f - fontRect.Height * 0.5f;

                        Bitmap numberBitmap = _getNumberBitmap( i );

                        g.DrawImage( numberBitmap, fontRect );
                        
                        ++i;
                    }
                }
            }
        }

        private void _pictureBox_SizeChanged( object sender, EventArgs e )
        {
            _updateVisuals();
        }

        private void _pictureBox_MouseDown( object sender, MouseEventArgs e )
        {
            if ( e.Button == MouseButtons.Right )
            {
                _draggingImage = true;
                _lastMouseLocation = e.Location;
            }
        }

        private void _pictureBox_MouseMove( object sender, MouseEventArgs e )
        {
            if ( _draggingImage )
            {
                Point location = e.Location;

                int sourceDeltaX = 
                    (int)( ( location.X - _lastMouseLocation.X ) / _zoomFactor );
                int sourceDeltaY = 
                    (int) ( ( location.Y - _lastMouseLocation.Y ) / _zoomFactor );

                _cameraPosition.X -= sourceDeltaX;
                _cameraPosition.Y -= sourceDeltaY;

                _correctCameraPosition();

                _updateVisuals();

                _lastMouseLocation = location;
            }
        }

        private void _pictureBox_MouseUp( object sender, MouseEventArgs e )
        {
            if ( e.Button == MouseButtons.Right )
            {
                _draggingImage = false;
            }
        }

        private void ImageMapInstance_Enter( object sender, EventArgs e )
        {
            _updateVisuals();
        }

        private void _correctCameraPosition()
        {
            if ( _originalImage == null )
                return;

            float bitmapWidth = _originalImage.Width * _zoomFactor;
            float bitmapHeight = _originalImage.Height * _zoomFactor;

            if ( bitmapWidth > _pictureBox.Width )
            {
                float sourceWidth = _pictureBox.Width / _zoomFactor;
                Debug.Assert( sourceWidth < _originalImage.Width );

                if ( _cameraPosition.X < 0.0f )
                {
                    _cameraPosition.X = 0;
                }

                if ( _cameraPosition.X + sourceWidth > _originalImage.Width )
                {
                    _cameraPosition.X = (int)( _originalImage.Width - sourceWidth );
                }
            }

            if ( bitmapHeight > _pictureBox.Height )
            {
                float sourceHeight = _pictureBox.Height / _zoomFactor;
                Debug.Assert( sourceHeight < _originalImage.Height );

                if ( _cameraPosition.Y < 0.0f )
                {
                    _cameraPosition.Y = 0;
                }

                if ( _cameraPosition.Y + sourceHeight > _originalImage.Height )
                {
                    _cameraPosition.Y = (int) ( _originalImage.Height - sourceHeight );
                }
            }
        }

        Point _pictureBoxToSourceImage( Point point )
        {
            float destX = point.X - _pictureOffset.X;
            float destY = point.Y - _pictureOffset.Y;

            return new Point( (int) ( destX / _zoomFactor ), (int) ( destY / _zoomFactor ) );
        }


        void _updateVisuals()
        {
            _drawBitmapToPictureBox( false );
            _drawFrameRects();

            if ( _imageMapEditor.ShowFrameNumbers )
            {
                _drawFrameNumbers();
            }

            _pictureBox.Refresh();
        }

        void _updateTitle()
        {
            if ( _neverSaved || _undoRecord.Version != _savedInstanceVersion )
            {
                Text = "*" + _title;
            }
            else
            {
                Text = _title;   
            }

            TabText = Text;
        }

        Bitmap _getNumberBitmap( int number )
        {
            if ( number >= _frameNumberCache.Count )
            {
                using ( Bitmap bitmap = new Bitmap( 256, 256 ) )
                {
                    using ( Graphics sg = Graphics.FromImage( bitmap ) )
                    {
                        sg.InterpolationMode = 
                            System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                        sg.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

                        using ( Brush brush = new SolidBrush( _imageMapEditor.Config.FrameNumberColorRt ) )
                        {
                            using ( Font font = new Font( "Arial", 100 ) )
                            {
                                while ( _frameNumberCache.Count <= number )
                                {
                                    sg.Clear( Color.Transparent );

                                    String text = _frameNumberCache.Count.ToString();
                                    SizeF stringSize = sg.MeasureString( text, font );

                                    Size bitmapSize = new Size();
                                    bitmapSize.Width = (int) Math.Ceiling( stringSize.Width );
                                    bitmapSize.Height = (int) Math.Ceiling( stringSize.Height );

                                    Bitmap cacheBitmap =
                                        new Bitmap( bitmapSize.Width, bitmapSize.Height );

                                    using ( Graphics dg = Graphics.FromImage( cacheBitmap ) )
                                    {
                                        dg.Clear( Color.Transparent );
                                        dg.DrawString( text, font, brush, PointF.Empty );
                                    }

                                    _frameNumberCache.Add( cacheBitmap );
                                }
                            }
                        }
                    }
                }
            }

            return _frameNumberCache[number];
        }

        void _clearFrameNumberCache()
        {
            foreach ( Bitmap bitmap in _frameNumberCache )
            {
                bitmap.Dispose();
            }

            _frameNumberCache.Clear();
        }
        #endregion
    }
}
